This document describes the grammar and the semantics of the ViSlang language. The grammar is given in EBNF as used and described in the W3C Recommendation that specifies XML. This document provides also notes about the actual implementation of the language. In some cases two equivalent EBNF grammars are mentioned. One of them being shorter and more comprehensible (in EBNF), the other one being the actual implementation. In these cases the longer grammar is implemented either for performance reasons or because of the easier generation of the AST which avoids complex tree rewriting and leads to faster execution.
The ViSlang parser is implemented as a recursive decent parser using the boost::spirit library. ViSlang is an interpreted language meaning that first a parser checks the syntactic rules of a program, and while doing so generates an abstract syntax tree (AST). If parsing is successful the interpreter afterwards runs, directly interpreting the AST. The semantic analysis phase is tightly integrated with the execution phase and performed by the interpreter during runtime. If a semantic error occurs a runtime error is generated, execution is stopped and the error is reported to the user. A semantic analysis phase prior to the execution phase is ommited to increases the runtime performance of ViSlang.
A valid program is a sequence of instructions and comments:
ListOfInstructions ::= ( Instruction | Comment ) *
Each instruction can be one of the following elements:
Instruction ::= IfStatement |
WhileStatement |
ForStatement |
( Assignment (";" | ":") ) |
( Linking (";" | ":") ) |
( Unlinking (";" | ":") )|
( Trigger (";" | ":") ) |
FunctionDeclaration |
( ReturnStatement (";" | ":") ) |
( VariableDeclaration (";" | ":") ) |
Slangblock |
( Expression (";" | ":") )
note that some statements (like assignment, expression, etc.) end with a semicolon or a colon and others don't. We will see that the ones that don't (like conditionals, function declarations, etc.) all end with a scoped code block:
ScopedCode ::= "{" ListOfInstructions "}"
The grammar of the if-statement consists of a if-clause that includes a boolean expression in brackets and a scoped code block. The if-clause is followed by any number of else if-clauses, again each of them including a boolean expression in brackets and a scoped code block. Lastly an optional else clause follows that consists of a scoped code block only.
IfStatement ::s= "if" "(" BooleanExpression ")" ScopedCode ( "else if" "(" BooleanExpression ")" ScopedCode )* ("else" ScopedCode)?
The grammar of the while statement is given as:
WhileStatement ::= "while" "(" BooleanExpression ")" ScopedCode
The scoped code block is repeatedly executed as often as the evaluation of the boolean expression yields true.
The grammar of the for loop consists of a loop head that includes a 3-tuple of statements separated by a semicolon all inside brackets. It is followed by a scoped code block. The 3-tuple consists of an initial pre-assignment (assignments may include variable declarations), a boolean expression that is evaluated each itereration before the code block is executed, and a post-assignment that is evaluated after every iteration.
ForStatement ::= "for" "(" Assignment ";" BooleanExpression ";" Assignment ")" ScopedCode
In conditionals as well as in loops curly brackets around the code block must not be ommited (unlike in many other programming languages).
The return statement consists of the keyword "return" and an expression.
ReturnStatement ::= "return" Expression
Executed in a function it causes to leave the (innermost) function and return the result of the expression. When executed outside functions it causes the stop of the execution of the current program.
The using statement consists of the "using" keyword followed by an identifier and either a "colon" symbol or a "semicolon" symbol. After that any code can follow that must not contain the "using" keyword. The using statement ends with the "using" keyword followed by a semicolon, or with the end of input:
SlangBlock ::= "using" Identifier [";"|":"] AnythingButUsing ( ("using" ";") | eoi)
The using statement will cause that the code block is forwarded to a slang specified by the identifier during parsing. The slang might reject the code (based on internal analysis) which leads to a parser error. If the code block is accepted by the slang during parsing, ViSlang calls the slang to exectute the slang block during the interpretation phase.
Functions are declared starting either with a type (the return type) or the keyword void, followed by an identifier and an variable declaration list in brackets. A scoped code block follows that implements the actual functionality. Optionally, an immediate function call might follow by providing an expression list in brackets as arguments for the function. If the function is called immediately, the return value optionally might be assigned to a variable using the -> symbol. A semicolon must follow an immediate function call.
The grammar of the function declaration (with optional immediate function call) is:
FunctionDeclaration ::= ( Type | "void" ) Identifier "(" ArgumentDeclarationList ")" ScopedCode
( "(" ExpressionList ")" ("->" Identifier)? ";" )?
where ExpressionList is defined as:
ExpressionList ::= (Expression ("," Expression)* )?
and ArgumentDeclarationList is defined as:
ArgumentDeclarationList ::= (VariableDeclaration ("," VariableDeclaration)* )?
Note that both lists may be empty (i.e., also the empty string matches).
The function call grammar is defined as:
FunctionCall ::= Identifier "(" ExpressionList ")"
Variables are declared either in a separate statement:
VariableDeclaration ::= Type Identifier
or combined with an assignment or with a linking instruction.
The assignment is given as:
Assignment ::= Type? Identifier "=" Expression
Variables are typed and type checks are performed prior to the execution of each instruction. Some types are compatible and are automatically converted. For instance
float f = 1;
as well as
integer i = 2;
float f = i;
will convert the integer value to a float value and assign it to variable f.
However,
float f = 0.0;
integer i = f;
as well as
integer i = 0.0;
will result in a runtime error.
Triggers are established between a variable and a function call with the "->" symbol. The trigger declaration optionally starts with a type allowing for a declaration of a new variable. Then an identifier of a variable follows. When the value of the variable is changed, the trigger executes the subsequent function call. Optionally, the result of the function call can be post-assigned to a variable that is specified using the -> symbol and an identifier subsequently. Note that the trigger declaration does NOT call the function immediately but only establishes the trigger. The (optional) post-assignment is called everytime the trigger is executed.
Trigger ::= Type? Identifier "->" FunctionCall ( "->" Identifier)?
Two variables can be linked by the "<=>" symbol and unlinked by the symbol ">=<". Linking means that the two variables are connected. When one of them gets assigned a new value the other one is updated as well. The rules for linking are more strict than for assignment. Variables can only be linked if they are of identical type, e.g.,
integer i = 2;
float f <=> i;
will result in a runtime error. Linking two variables will initially assign the value of the right variable to the left variable. For instance, after executing:
integer i1 = 1;
integer i2 = 2;
i1 <=> i2;
both variables (i1 and i2) will be 2.
The grammar for linking is:
Linking ::= Type? Identifier "<=>" Identifier
The Type at the beginning is optional. When declared the variable of name Identifer is declared first.
To break the link between two variables the >=< symbol is used. If the two variables are not linked the instruction is ignored. The grammar for unlinking is given as:
Unlinking ::= Identifier ">=<" Identifier
An expression is conceptually a tree that consists of operation nodes and leaf nodes.
A leaf node of an expression is given by a terminal expression. Literals, identifiers, and function calls are the three different terminal expressions:
TerminalExpression ::= Literal | FunctionCall | Identifier
There are different kinds of Literals that are recognized in the following order:
Literal ::= StringLiteral | FloatLiteral | IntegerLiteral | BooleanLiteral
Float literals are numbers of the following syntax:
FloatLiteral ::= ("-" number "." number) | (number "." number)
number ::= [0-9]+
Integer literals look like this
IntegerLiteral ::= ("-" number) | (number)
number ::= [0-9]+
String literals are defined as:
StringLiteral ::= "'" [\w\s]* "'"
and Boolean literals are written as:
BooleanLiteral ::= "true" | "false"
For Boolean expressions operators of increasing precedence are listed. Being of higher precedence means a operatorLower b operatorHigher c is equivalent to a operatorLower (b operatorHigher c) For instance false == false | true is false, since it is evaluated as false == (false | true)
'greater'' >, 'less' <, 'greater or equal' >=, 'less or equal' <=, 'equal' ==, and 'not equal' != are defined. They are binary operators, meaning that for example a<b<c is not defined. Their precendece is 1.
The operators 'and' & and 'or' | are left associative operators of precedence 2. Left associative means a operator1 b operator2 c is equivalent to
(a operator1 b) operator2 c For instance false & true | true is true, since it is equivalent to (false & true) | true unlike the right associative case false & (true | true) which evaluates to false.
The unary 'not' "!" operator is of highest precedence.
A valid Boolean expression consists of Boolean terms:
BooleanTerm ::= BooleanFactor
(
("&" BooleanFactor) |
("|" BooleanFactor)
)?
(
("&" BooleanFactor) |
("|" BooleanFactor)
)*
which is equivalent to
BooleanTerm ::= BooleanFactor ( ("&" BooleanFactor) | ("|" BooleanFactor) )*
Boolean factors are defined as:
BooleanFactor ::= ( TerminalExpression | ( "(" BooleanExpression ")" ) )
|
(
"!"
( TerminalExpression | ( "(" BooleanExpression ")" ) )
)
Arithmetic expressions are composed of the lower precedence binary operators 'add' +, and 'substract' -, as well as the higher precedence binary operators 'multiply' *, 'divide' /, and 'modulo' %. Again everything is left associative. The highest precedence operator is the unary 'minus' -.
A valid arithmetic expression consists of arithmentic terms:
ArithmeticTerm ::= ArithmeticFactor
(
( "*" ArithmeticFactor ) |
( "/" ArithmeticFactor ) |
( "%" ArithmeticFactor )
)?
(
( "*" ArithmeticFactor ) |
( "/" ArithmeticFactor ) |
( "%" ArithmeticFactor )
)*
which is equivalent to
ArithmeticTerm ::= ArithmeticFactor ( ("*"|"/"|"%") ArithmeticFactor ) )*
Arithmetic factors are defined as:
ArithmeticFactor ::=
( "-" ( TerminalExpression | "(" ArithmeticExpression ")" ) )
|
( TerminalExpression | "(" ArithmeticExpression ")" )
Comments are either single line comments or multi-line comments:
Comment ::= ( MultilineComment | SinglelineComment )
SinglelineComment ::= ("/" "/" AnythingButEol (eol | eoi))
MultilineComment ::= ("/" "*" AnythingButEndOfMultilineComment "*" "/")
Identifiers are defined as upper and lower case letters:
Identifier ::= [\w\s]*
Type is defined as one of the following strings:
Type ::= "var" | "float" | "float2" | "float3" | "float4" | "boolean" | "integer" | "string" | "list" | "image" | "volume" | "fvoxel" | "bvoxel" | "uvoxel" | "ivoxel" | "vset" | "vlabel" | "slang" | "object"